package com.jxpanda.starter.config.security.token;

import com.jxpanda.commons.constant.StringConstant;
import com.jxpanda.commons.key.RSAKeyHelper;
import com.jxpanda.commons.toolkit.ObjectKit;
import com.jxpanda.commons.toolkit.RSAKit;
import com.jxpanda.commons.toolkit.StringKit;
import com.jxpanda.commons.toolkit.json.JsonKit;
import com.jxpanda.starter.config.security.AuthenticationProperties;
import io.jsonwebtoken.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Panda
 */
@Data
@Slf4j
public abstract class AbstractTokenHelper {

    private static final String RSA_FAMILY_NAME = "RSA";

    protected SignatureAlgorithm algorithm;

    private int tokenLive;

    protected String key;

    protected PublicKey publicKey;

    protected PrivateKey privateKey;

    protected AbstractTokenHelper(AuthenticationProperties authenticationProperties) {
        this.algorithm = authenticationProperties.getAlgorithm();
        this.tokenLive = authenticationProperties.getTokenLive();
        this.key = authenticationProperties.getKey();
        if (usingKeyPair(this.algorithm)) {
            RSAKeyHelper rsaKeyHelper = RSAKit.keyHelper();
            if (ObjectKit.isEmpty(authenticationProperties.getPublicKey()) || ObjectKit.isEmpty(authenticationProperties.getPrivateKey())) {
                KeyPair keyPair = rsaKeyHelper.generateKeyPair();
                this.publicKey = keyPair.getPublic();
                this.privateKey = keyPair.getPrivate();
            } else {
                this.publicKey = rsaKeyHelper.loadPublicKey(authenticationProperties.getPublicKey());
                this.privateKey = rsaKeyHelper.loadPrivateKey(authenticationProperties.getPrivateKey());
            }
        }
    }

    /**
     * 签名函数
     *
     * @param jwtBuilder JwtBuilder
     * @return 入参原样返回，支持后续的链式调用
     */
    protected abstract JwtBuilder signWith(JwtBuilder jwtBuilder);

    /**
     * 校验签名前，需要设置密钥
     * 这个密钥对于不同的算法会不一样（RSA要使用【privateKey】，而HMAC使用【key】）
     * 所以需要由子类来选择密钥
     *
     * @param jwtParser jjwt的builder函数
     * @return 入参原样返回，支持后续的链式调用
     */
    protected abstract JwtParser setSigningKey(JwtParser jwtParser);

    public Token signature(Token token) {
        if (!token.isEffective()) {
            return token;
        }
        token.setSignature(signWith(Jwts.builder()
                .setPayload(JsonKit.toJson(token)))
                .compact());
        return token;
    }

    public Token validate(String token) {
        if (StringKit.isBlank(token)) {
            return Token.empty();
        }
        try {
            return setSigningKey(Jwts.parser()).parse(token, handler());
        } catch (Exception ex) {
            return Token.empty();
        }
    }

    public Token createToken(String id, String username, String password, List<String> authorities) {
        return Token.builder()
                .id(id)
                .name(username)
                .password(password)
                .expireTime(LocalDateTime.now().plusSeconds(tokenLive))
                .authorities(authorities.stream().map(Authority::new).collect(Collectors.toList()))
                .build();
    }

    public Token createToken(String id, String username, String password) {
        return createToken(id, username, password, new ArrayList<>());
    }

    public Token createToken(String id, String username, List<String> authorities) {
        return createToken(id, username, StringConstant.BLANK, authorities);
    }

    public Token createToken(String id, String username) {
        return createToken(id, username, StringConstant.BLANK, new ArrayList<>());
    }

    /**
     * 返回当前算法是否是使用密钥对加密
     * 目前RSA算法会使用密钥对，未来计划支持ECDSA算法
     */
    public static boolean usingKeyPair(SignatureAlgorithm algorithm) {
        return RSA_FAMILY_NAME.equals(algorithm.getFamilyName());
    }


    private static Handler handler() {
        return Handler.INSTANCE;
    }

    private static class Handler extends JwtHandlerAdapter<Token> {
        // 给一个单例
        private static final Handler INSTANCE = new Handler();

        @Override
        public Token onClaimsJws(Jws<Claims> jws) {
            return JsonKit.fromJson(JsonKit.toJson(jws.getBody()), Token.class);
        }
    }

}
